﻿using System;
using System.Linq;
using Web7.ExceptionHandling;

namespace Web7
{
	public static class ExceptionExtension
	{
		public const string ExceptionLevelDataKey = "__exception_level__";

		/// <summary>
		/// 为异常设置级别。
		/// </summary>
		/// <remarks>
		/// 将使用 <seealso cref="Exception.Data"/> 存储级别。
		/// </remarks>
		/// <param name="exception"></param>
		/// <param name="level"></param>
		public static void SetLevel(this Exception exception, OperationProcessLevel level)
		{
			exception.Data[ExceptionLevelDataKey] = level;
		}
		/// <summary>
		/// 获取已设置在异常上的级别。
		/// 默认值是 <seealso cref="OperationProcessLevel.Error"/>
		/// </summary>
		/// <param name="exception"></param>
		/// <returns></returns>
		public static OperationProcessLevel GetLevel(this Exception exception)
		{
			var level = exception.Data[ExceptionLevelDataKey];

			if (null == level ||
				!(level is OperationProcessLevel))
			{
				// 设置默认级别
				PrepareSetLevel(exception);
			}

			return (OperationProcessLevel)(exception.Data[ExceptionLevelDataKey]);
		}
		/// <summary>
		/// 发布异常信息
		/// </summary>
		/// <param name="exception"></param>
		/// <returns>
		/// 如果返回 true，则表示已发布异常
		/// </returns>
		public static bool Publish(this Exception exception)
		{
			PrepareSetLevel(exception);
			return PublishException(exception);
		}
		/// <summary>
		/// 发布异常信息
		/// </summary>
		public static bool Publish(this Exception exception, OperationProcessLevel level)
		{
			exception.SetLevel(level);
			return PublishException(exception);
		}
		/// <summary>
		/// 将异常发布到指定<see cref="IExceptionPublisher"/>类型的异常发布者
		/// </summary>
		public static bool PublishTo<T>(this Exception exception) where T : IExceptionPublisher
		{
			PrepareSetLevel(exception);
			return PublishExceptionTo<T>(exception);
		}
		/// <summary>
		/// 将异常发布到指定<see cref="IExceptionPublisher"/>类型的异常发布者
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="exception"></param>
		/// <param name="level"></param>
		/// <returns></returns>
		public static bool PublishTo<T>(this Exception exception, OperationProcessLevel level) where T : IExceptionPublisher
		{
			exception.SetLevel(level);
			return PublishExceptionTo<T>(exception);
		}
		/// <summary>
		/// 将异常发布到指定的<see cref="IExceptionPublisher"/>实例
		/// </summary>
		/// <param name="exception"></param>
		/// <param name="publisher"></param>
		public static void PublishTo(this Exception exception, IExceptionPublisher publisher)
		{
			if (null == publisher)
			{
				return;
			}

			PrepareSetLevel(exception);
			publisher.Publish(exception);
		}
		/// <summary>
		/// 将异常发布到指定的<see cref="IExceptionPublisher"/>实例
		/// </summary>
		/// <param name="exception"></param>
		/// <param name="publisher"></param>
		/// <param name="level"></param>
		public static void PublishTo(this Exception exception, IExceptionPublisher publisher, OperationProcessLevel level)
		{
			if (null == publisher)
			{
				return;
			}

			exception.SetLevel(level);
			publisher.Publish(exception);
		}

		static void PrepareSetLevel(Exception ex)
		{
			var data = ex.Data[ExceptionExtension.ExceptionLevelDataKey];
			if (null != data &&
				data is OperationProcessLevel)
			{
				return;
			}

			OperationProcessLevel level;

			// TODO: 添加更多预处理
			if (ex is NotImplementedException ||
				(ex is ArgumentNullException) ||
				(ex is ArgumentException))
			{
				level = OperationProcessLevel.Warning;
			}
			else if (ex is SystemException)
			{
				level = OperationProcessLevel.Error;
			}
			else
			{
				level = OperationProcessLevel.Warning;
			}
			ex.SetLevel(level);
		}
		
		static bool PublishException(Exception ex) 
		{
			var publishers = ExceptionPublisherManager.AllPublishers;
			var any = false;

			foreach (var p in publishers)
			{
				any = true;
				p.Publish(ex);
			}

			return any;
		}

		static bool PublishExceptionTo<T>(Exception ex) where T : IExceptionPublisher
		{
			var publishers = ExceptionPublisherManager.AllPublishers.Where(p => p is T);
			var any = false;

			foreach (var p in publishers)
			{
				any = true;
				p.Publish(ex);
			}

			return any;
		}
	}
}
